#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Report.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This file defines the abstract Report class, the 
#               base class of all Model Reports.
#
# See Also:     GenericObject
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Common import *
from Products.KnowledgeEngine.Core import *
from GenericObject import GenericObject, GenericObjectInfo
from PropertyDefinition import PropertyDefinition
from PropertyTypes import *
from PropertyFormats import *
from PropertyStructures import *
from KERFProcessor import *
from EventDefinition import *
from PermissionDefinition import *
from SimpleWizard import *
from WizardState import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile
from Acquisition import *

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## Exception Definitions
class ReportException(Exception):
    """ Defines any KE specific exception generated while rendering a report """
    pass


#####################################################################
## ReportInfo Class Definition
class ReportInfo(GenericObjectInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'Report'

    #---------------------------------------------------------------#

    def getClassFamilyName(self):
        """
            Return the name of the family to which the the
            class (and subclasses) and it's subclasses belong/
            
            This is used to enable searching for families of 
            generic objects, eg: 'EntitySource' instances.
        """

        #all entity sources belong to the 'EntitySource' family
        return 'Report'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Report"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "report"

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.
            
            Override an return true if the instance is to be
            a container for other instances.
            
            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        #by default, Reports are containers (they may contain images etc)
        return true          

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified 
            by the className
        """

        return className == 'Report' or GenericObjectInfo.isInstanceOf(self, className)
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #DEFAULT RULE: Reports are permitted in Models and Repositories
        return containerClassInfo.isInstanceOf('Model') or containerClassInfo.isInstanceOf('Repository')

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: Reports don't contain anything but events 
        return childClassInfo.isInstanceOf('Event')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        GenericObjectInfo.onConfigureProperties(self)
     
     #---------------------------------------------------------------#
 
    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class events for reflection
        GenericObjectInfo.onConfigureEvents(self)

        #add the onBeforeView event (which occurs prior to a report being generated)
        self.addEventDefinition(EventDefinition(identity="onGenerate", displayName="When Report is Generated", description="Occurs when a report is generated", eventType=et_SERVER))
    
    #---------------------------------------------------------------#

    def onConfigurePermissions(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific permission definitions.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        

        #initialise the super-class permissions for reflection
        GenericObjectInfo.onConfigurePermissions(self)
        
        #add a definition for the 'ViewReport' permission
        self.addPermissionDefinition(PermissionDefinition(identity="ViewReport", displayName="View Report", description="Permission to view a report for the <b>accessible</b> assessments", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))
        
    #---------------------------------------------------------------#

#####################################################################
## Report Class Definition
class Report(GenericObject, SimpleWizard):
    """
        Base class for all KnowledgeEngine Reports
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = ReportInfo()

    #---------------------------------------------------------------#
    # Volatile Attributes
    _v_Model = None            #the model to which the report belongs

    #---------------------------------------------------------------#

    # Custom zope manage_preview to permit previews of Reports.
    preview_html = DTMLFile('dtml/preview', globals())

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the Report"        
        
        GenericObject.__init__(self, id, identity)
        SimpleWizard.__init__(self)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """

        #preform default onAfterInstanciation processing
        GenericObject.onAfterInstanciation(self)
        
        ###print "DEBUG: Report.onAfterInstanciation: Completed"
        
    #---------------------------------------------------------------#

    def onAfterPropertyChange(self, propertyName, oldValue, newValue):
        "Handle when an object property has changed (using the property editor)"

        #perform default processing
        GenericObject.onAfterPropertyChange(self, propertyName, oldValue, newValue)
       
    #---------------------------------------------------------------#

    def isPreviewPermitted(self):
        """
            Return if the Report is allowed to be previewed.
            
            By default all Reports can't be previewed, some however
            should can be previewed. eg: HTMLHeaderFooters
        """
        
        return false
        
    #---------------------------------------------------------------#

    def hasOwnOrder(self):
        """
            Returns if the Report defines it's own order and should not
            be destroyed by updateDisplayPositionInModels method
        """
        
        #DEFAULT: updateDisplayPositionInModels defines order
        return false
        
    #---------------------------------------------------------------#

    def isDisplayedBeforeComponents(self):
        """
            Returns if the Report is to be displayed before
            components in a model. Alternatively the Report is
            displayed after the components in a model.
            (If the Report is in a model)
        """
        
        #DEFAULT: displayed after components
        return false

    #---------------------------------------------------------------#

    def getModel(self):
        """
            Returns the Model in which this report is located.
        """

        parent = self.getParent()
        
        while not parent.isInstanceOf("Model"):
            parent = parent.getParent()    

        return parent
        
    #---------------------------------------------------------------#

    def getDefaultExitURL(self, request):
        """
            ABSTRACT: Returns the default URL to be used
            when exiting a report            
        """

        ###determine the exit URL (where to go if we close/cancel this report)
        portal = self.portal_url.getPortalObject()
        return '%s/myknowledge' % (portal.absolute_url())
        
    #---------------------------------------------------------------#

    def prepareDefaultWizardState(self, request, wizardState):
        """
            OVERRIDE: Prepares and adds any necessary additional
            state from the request to the wizard state.
            
            This method is called prior to initialise wizardState
            prior to generating a report and prior to attempting
            to collect wizard state from the wizard steps.
            
            You SHOULD capture any properties from the request HERE
            if you want to pass then to a report.

        """

        #by default we don't do anything.
        pass
        
    #---------------------------------------------------------------#

    def generate(self, REQUEST, **args):
        """
            FINAL: Handles the GET and POST requests when 
            generating a report
        """           
        #get the user id of the currently authenticated user
        zopeUserIdentity = REQUEST["AUTHENTICATED_USER"].getId()
        userIdentity = iif(zopeUserIdentity is None, "anonymous", zopeUserIdentity)
    
        #get the User from the UserProviderService (we may even store it in the session!)
        userProviderService = getUserProviderService(self)
        user = userProviderService.getUser(userIdentity)
    
        #get the security service... so we can check security
        securityService = getSecurityService(self)

        #get the credit management service... we'll need to check some credits
        creditManagementService = getCreditManagementService(self)
    
        #let's use a sensible case for the REQUEST
        request = REQUEST
    
        #get the session from the request
        session = request["SESSION"]
        
        #get the response from the request (weird I know, but that's where it lives in Zope)
        response = request["RESPONSE"]
        
        #get the current wizard state (if there is one)
        if request["REQUEST_METHOD"] == "POST" and session.has_key('wizardState'):            
            wizardState = session.get('wizardState')

            #get the opened from repository and exit URL from the wizard state
            openedFromRepositoryIdentity = wizardState.getPropertyValue("openedFromRepositoryIdentity")
            exitURL = wizardState.getPropertyValue("exitURL")
        else:
            #create new wizard state
            wizardState = WizardState()

            #initialise the wizard state with default property state
            for propertyDefinition in self.getPropertyDefinitionsForWizard(wizardState, self):
                wizardState.setPropertyValue(propertyDefinition.getIdentity(), propertyDefinition.getDefaultValue())

            ###determine the exit URL (where to go if we close/cancel this report)
            portal = self.portal_url.getPortalObject()
            if request["REQUEST_METHOD"] == "POST" and request.form.has_key('exitURL'):
                exitURL = request.form['exitURL']

            elif request.has_key("exitURL"):
                #grab the entire query string and chop of the bit before the exitURL
                exitURL = request["QUERY_STRING"]
                exitURL = exitURL[exitURL.find("exitURL") + 8:]

            else:
                #no exit specified... let's use a default
                exitURL = self.getDefaultExitURL(request)

            #add the exitURL to the wizard state
            wizardState.setPropertyValue("exitURL", exitURL)

            ###determine the repository from which the report is being opened
            if request["REQUEST_METHOD"] == "POST" and request.form.has_key('openedFrom'):
                openedFromRepositoryIdentity = request.form['openedFrom']

            elif request.has_key("openedFrom"):
                openedFromRepositoryIdentity = request["openedFrom"]
            else:
                openedFromRepositoryIdentity = None
            if openedFromRepositoryIdentity == '':
                openedFromRepositoryIdentity = None

            #add the repositoryIdentity to the wizard state
            wizardState.setPropertyValue("openedFromRepositoryIdentity", openedFromRepositoryIdentity)

            #now prepare any additional wizard state for the report
            self.prepareDefaultWizardState(request, wizardState)
        
        ###does the user have persmission to view this report? 
        #assume the user doesn't have permission
        hasPermission = false

        #does the user have 'AccessModel' permission
        accessModelPermission = securityService.getEffectivePermission("AccessModel", self.getModel().getIdentity(), user.getIdentity())  
        if accessModelPermission is not None and accessModelPermission.granted():
            #we permission so far!
            hasPermission = true

            #is the report in a repository?
            if self.getParent().isInstanceOf("Repository"):
                #does the user have 'AccessRepository' permission
                accessRepositoryPermission = securityService.getEffectivePermission("AccessRepository", self.getParent().getIdentity(), user.getIdentity())        
                if accessRepositoryPermission is None or accessRepositoryPermission.denied():
                    #the user doesn't have access
                    hasPermission = false

            #does the user have 'ViewReport' permission
            if hasPermission:
                viewReportPermission = securityService.getEffectivePermission("ViewReport", self.getIdentity(), user.getIdentity())
                if viewReportPermission is not None and viewReportPermission.granted():
                    #the user has permission!
                    hasPermission = true
                else:
                    #the user doesn't have access
                    hasPermission = false
        else:
            #the user doesn't have access
            hasPermission = false

        #can the user view the report?
        if not hasPermission:
            #the user doesn't have assess permission
            #(let's so we will redirect them back to the exitURL)
            failureMessage = "Sorry.+You+don't+have+permission+to+view+the+'%s'" % (self.getTitleOrId(),)
            exitURL = exitURL + iif(exitURL.find('?') < 0, '?', '&') + 'portal_status_message=' + failureMessage
            return response.redirect(exitURL)
        
        #user has permission to render the report!
        else:
            #do we need to process wizard state?
            if self.getTotalWizardSteps(wizardState, self) > 0:
                #do we need to render the wizard editor? (assume no)
                renderWizard = false
                
                #process the wizard action
                if request["REQUEST_METHOD"] == "POST":
                    #determine all of the editlets for properties of the current wizardState, and have them work out and set the
                    #appropriate property values in the wizardState using the information contained in the posted REQUEST.
                    for propertyDefinition in self.getPropertyDefinitionsForWizardStep(wizardState, self):

                        #get the property editlet (using the current value of the property)
                        editlet = propertyDefinition.getEditlet(propertyAccess=wizardState, context=self)

                        #does the property have an editlet
                        if editlet != None:
                            #have the editlet set the new value from the request
                            editlet.updatePropertyWith(propertyDefinition, wizardState, REQUEST)
                        else:
                            print 'no editlet for %s' % propertyDefinition.getIdentity()

                    #what was the wizard action? cancel, back or next?
                    #cancel?
                    if request.has_key("button.cancel"):
                        return response.redirect(exitURL)

                    #back?
                    elif request.has_key("button.back"):
                        #always render on 'back'
                        renderWizard = true

                        #move backwards iff we aren't at the first step
                        if not self.isAtFirstWizardStep(wizardState, self):
                            wizardState.previousWizardStep()

                    #next?
                    elif request.has_key("button.next"):
                        #always render on 'next'
                        renderWizard = true                            

                        #move forwards iff we aren't at the last step
                        if not self.isAtLastWizardStep(wizardState, self):
                            wizardState.nextWizardStep()

                    #finished!                    
                    else:
                            #now render the report!
                            renderWizard = false
                
                else:
                    #a get request so we must render the wizard
                    renderWizard = true

                #add/update the wizardState to the session
                session['wizardState'] = wizardState
                    
                #render the wizard step?
                if renderWizard:
                    #use the wizard editor to get the wizard state for the report
                    return self.Report_wizard_form(REQUEST=request, REPORT=self, WIZARDSTATE=wizardState)

            #set the current time (so we can output when the report was generated)
            request["NOW"] = DateTime()

            #set the elements in the request (used to help render the report)
            request["ELEMENTS"] = self.getAcquiredElementsDictionary().values()            
            request["ELEMENTS_BY_TYPE"] = self.getAcquiredElementsByTypeDictionary().values()

            #set the current user to the request (so we can pass it around the report while rendering as well)
            request["USER"] = user

            #add the wizard state to the request (so that all report components can see it)
            request["WIZARD_STATE"] = wizardState

            #now render the report
            return self.renderReport(request, wizardState, **args)

    #---------------------------------------------------------------#

    def renderReport(self, request, wizardState, **args):
        """
            ABSTRACT: Renders the report content (called by Report.generate)
            
            NOTE: Override this method to perform specific
            report generation/rendering using the provided
            parameters
        """

        return ""

    #---------------------------------------------------------------#

    def resolveKERFProperties(self, kerf, assessment, component=None):
        """
            Resolves the <$ ... $> component property references
            in the specified kerf using the provided assessment 

            RETURNS: a python expression that may be evaluated with eval
        """

        return resolveKERFProperties(kerf, assessment, component)
    
    #---------------------------------------------------------------#

    def resolveKERF(self, content, assessment, component=None):
        """
            Resolves the <kerf>...</kerf> (Knowledge Engine Report Fields)
            markups in the specified content (a string) given an
            assessment.
        """

        return resolveKERF(content, assessment, component)

    #---------------------------------------------------------------#

    def getWizardStepFinishButtonTitle(self, wizardState, context=None):
        """
            Return the title of the finished button for the
            wizard step using the provided wizard state.
            
            By default returns 'Finish...'.  Override this
            method if you'd like something else.

            RETURN: String
        """            
        
        return "Generate Report..."
        
    #---------------------------------------------------------------#
    
#####################################################################
## Class Initialisation
# (none)
